%% Author: GUWENQING
%% Created: 2012-4-5
%% Description: TODO: Add description to Gossip
-module(gossip).

%%
%% Include files
%%

%%
%% Exported Functions
%%
-export([run/10, init_random/0, random/2]).

%%
%% API Functions
%%

init_random() ->
	random:seed(now()),
	random:seed(random:uniform(1024),random:uniform(1024),random:uniform(1024)).

random(Min, Max) ->
	MinInt = erlang:round(Min),
	MaxInt = erlang:round(Max),
	random:uniform(MaxInt-MinInt+1)+MinInt-1.

allocate(N, Components) ->
	allocate(N, [], Components, {1, 0, 0, []}, lists:nth(1, Components)).

allocate(N, Inits, Components, Node, Component) ->
	%%Assume 1000-NUsedCPU/1000-NUsedMEM/CCPU/CMEM will never be ZERO
	{NID, NUsedCPU, NUsedMEM, NCurrentPlan} = Node,
	
	% IF finished
	if(Component == null)->
		  if(NID > N)->
				Inits;
			true->
				InitsNew = erlang:append(Inits, [Node]),
				allocate(N, InitsNew, NID+1)
		  end;
	  true ->
		  {CID, CCPU, CMEM} = Component,
		  if(NID > N)->
				failed;
			true->
				if
					NUsedMEM + CMEM =< 1000 ->
						TakenCPU = erlang:min(1000-NUsedCPU, CCPU),
						NUsedCPUNew = NUsedCPU + TakenCPU,
						NUsedMEMNew = NUsedMEM + CMEM,
						NCurrentPlanNew = lists:append(NCurrentPlan, [{CID, TakenCPU, CMEM}]),
						CCPUNew = CCPU - TakenCPU,
						if
							CCPUNew == 0 ->
								if
									CID + 1 =< length(Components)->
										ComponentNew = lists:nth(CID+1, Components);
									true->
										ComponentNew = null
								end;
							true->
								ComponentNew = {CID, CCPUNew, CMEM}
						end,
								
						if
							NUsedCPUNew == 1000 ->
								InitsNew = lists:append(Inits, [{NID, NUsedCPUNew, NUsedMEMNew, NCurrentPlanNew}]),
								allocate(N, InitsNew, Components, {NID+1, 0, 0, []}, ComponentNew);
							true->
								allocate(N, Inits, Components, {NID, NUsedCPUNew, NUsedMEMNew, NCurrentPlanNew}, ComponentNew)
						end;
					true ->
						InitsNew = lists:append(Inits, [Node]),
						allocate(N, InitsNew, Components, {NID+1, 0, 0, []}, Component)
				end
		  end
	end.

allocate(N, Inits, NID) ->
	if
		NID > N ->
			Inits;
		true ->
			allocate(N, lists:append(Inits, [{NID, 0, 0, []}]), NID+1)
	end.
					
	

%%
%% Actual entry point, mainly spawn T new gossip nodes and one gossip_monitor node, and then initialize
%% all the gossip nodes with information of neighbor information as well as MonitorPid.
%%
%% Wait until received "finish" (success) message from gossip_monitor. 
%%
%% Parameters:
%% 	T: Number of nodes
%% 	Target: Largest possible initial CPU configuration value in the node
%%  
%% Returns:
%% 	{Inits, Result}: initial ramdomed CPU configuration value and configuration value after sucessfully completed
%% 	Gossip based resource allocation operation.
%%

%% CPU0 = 1000, MEM0 = 1000
%% N>1
%% 
run(N, Vc, Vm, M, CPUMin, CPUMax, MEMMin, MEMMax, EndType, EndCheckValue) ->
	init_random(),
	CIDs = lists:seq(1,M),
	Components = [{CID, random(CPUMin, CPUMax), random(MEMMin, MEMMax)} || CID <- CIDs],
	Inits = allocate(N, Components),
	if
		Inits /= failed ->			
			Nodes = [{NID, spawn_link(gossip_node, init, [NID, Vc, Vm])}  || {NID, _UsedCPU, _UsedMEM, _Plan} <- Inits],
			MonitorPid = spawn_link(gossip_monitor, init, [Inits, Nodes, self(), EndType, EndCheckValue]),
			
			[Pid ! {start, self(), MonitorPid, Nodes, lists:nth(NID, Inits)}  || {NID, Pid} <- Nodes],
			
			receive
				{finish, GlobalList, StatList} ->
							{Inits, GlobalList, StatList}
			end
	end.

